import bpy
from mathutils import Matrix
import bmesh
from bpy.types import Operator
from math import sqrt


class Draw_Sphere_Op(Operator):
    bl_idname = "object.draw_sphere"
    bl_label = "draw sphere"
    bl_description = "draw a sphere"
    
    scale = 1
    subdiv = 3 # 球面平滑次数，一般不要大于5


    verts = []
    
    
    faces = [
            # 5 faces around point 0
            [0, 11, 5],
            [0, 5, 1],
            [0, 1, 7],
            [0, 7, 10],
            [0, 10, 11],

            # Adjacent faces
            [1, 5, 9],
            [5, 11, 4],
            [11, 10, 2],
            [10, 7, 6],
            [7, 1, 8],

            # 5 faces around 3
            [3, 9, 4],
            [3, 4, 2],
            [3, 2, 6],
            [3, 6, 8],
            [3, 8, 9],

            # Adjacent faces
            [4, 9, 5],
            [2, 4, 11],
            [6, 2, 10],
            [8, 6, 7],
            [9, 8, 1],
        ]

    middle_point_cache = {} # 存重点，避免公共边重复切割情况



    def vertex(self,x, y, z):
        length = sqrt(x**2 + y**2 + z**2)
        return [(i * self.scale) / length for i in (x,y,z)]

    def middle_point(self,point_1, point_2):
        """ 寻找重点，并放入一个缓存字典,缓存字典用大点到小点的方式代表 """

        # We check if we have already cut this edge first
        # to avoid duplicated verts
        smaller_index = min(point_1, point_2)
        greater_index = max(point_1, point_2)

        key = '{0}-{1}'.format(smaller_index, greater_index)

        if key in self.middle_point_cache:
            return self.middle_point_cache[key]

        # If it's not in cache, then we can cut it
        vert_1 = self.verts[point_1]
        vert_2 = self.verts[point_2]
        middle = [sum(i)/2 for i in zip(vert_1, vert_2)]

        self.verts.append(self.vertex(*middle))

        index = len(self.verts) - 1
        self.middle_point_cache[key] = index

        return index


    def execute(self,context):

        name = 'Icosomething'
        PHI = (1 + sqrt(5)) / 2

        self.verts = [
            self.vertex(-1,  PHI, 0),
            self.vertex( 1,  PHI, 0),
            self.vertex(-1, -PHI, 0),
            self.vertex( 1, -PHI, 0),

            self.vertex(0, -1, PHI),
            self.vertex(0,  1, PHI),
            self.vertex(0, -1, -PHI),
            self.vertex(0,  1, -PHI),

            self.vertex( PHI, 0, -1),
            self.vertex( PHI, 0,  1),
            self.vertex(-PHI, 0, -1),
            self.vertex(-PHI, 0,  1),
        ]

        for i in range(self.subdiv):
            faces_subdiv = []

            for tri in self.faces:
                v1 = self.middle_point(tri[0], tri[1])
                v2 = self.middle_point(tri[1], tri[2])
                v3 = self.middle_point(tri[2], tri[0])

                faces_subdiv.append([tri[0], v1, v3])
                faces_subdiv.append([tri[1], v2, v1])
                faces_subdiv.append([tri[2], v3, v2])
                faces_subdiv.append([v1, v2, v3])

            self.faces = faces_subdiv


        # -----------------------------------------------------------------------------
        # Add Object to Scene

        mesh = bpy.data.meshes.new(name)
        mesh.from_pydata(self.verts, [], self.faces)

        obj = bpy.data.objects.new(name, mesh)
        bpy.context.scene.collection.objects.link(obj)

        bpy.context.view_layer.objects.active = obj
        obj.select_set(True)


        # -----------------------------------------------------------------------------
        # Smoothing

        #bpy.ops.object.shade_smooth()

        for face in mesh.polygons:
            face.use_smooth = True
        
        return {'FINISHED'}




class Draw_BSphere_Op(Operator):
    bl_idname = "object.draw_sphere"
    bl_label = "draw sphere"
    bl_description = "draw a sphere"
    
    def execute(self,context):
        # Create a new BMesh instance.
        bm = bmesh.new()

        # Arguments for icosphere primitive.
        subdivisions = 3
        diameter = 0.5
        calc_uvs = True

        # Create an identity 4 x 4 affine transform matrix:
        # [(1.0, 0.0, 0.0, 0.0),
        #  (0.0, 1.0, 0.0, 0.0),
        #  (0.0, 0.0, 1.0, 0.0),
        #  (0.0, 0.0, 0.0, 1.0)]
        transform = Matrix.Identity(4)

        # bmesh.ops contains primitive creation functions.
        # The first argument, the BMesh is positional; the
        # ensuing arguments are preceded by a keyword.
        bmesh.ops.create_icosphere(
            bm,
            subdivisions=subdivisions,
            diameter=diameter,
            matrix=transform,
            calc_uvs=calc_uvs)

        # Create new mesh data.
        mesh_name = "Icosphere"
        mesh_data = bpy.data.meshes.new(mesh_name)

        # Convert BMesh to mesh data, then release BMesh.
        bm.to_mesh(mesh_data)
        bm.free()

        # Create a new object which contains the mesh data.
        mesh_obj = bpy.data.objects.new(mesh_data.name, mesh_data)

        # Link the object to the scene collection.
        bpy.context.collection.objects.link(mesh_obj)
        return {'FINISHED'}